import _ from 'lodash'
import Wave from '@/utils/wave'
import authority from './authority-config'

import { MessageBox } from 'element-ui'
import { getPropByPath } from '@/utils/util'
import { isFunction, onceError, isPromise, noop, formatTime } from './helper'
import { cloneElement } from '@/utils/vnode'
import { staticBaseURL, uploadURL, accessToken } from '@/env'
import { REQUEST_CANCEL_MESSAGE, REQ_TIMEOUT } from './constants'

const debounceCallFunction = fn => _.debounce(fn, 1000, {
  'leading': true,
  'trailing': false
})

const Plugin = {
  install (Vue) {
    ;[
      'merge',
      'cloneDeep',
      'isEmpty',
      'isEqual',
      'isNumber',
      'toNumber',
      'isNaN',
      'difference',
      'unionWith',
      'uniqBy',
      'differenceBy'
    ].forEach(method => {
      Vue.prototype[`$${method}`] = function () {
        return _[method].apply(_, arguments)
      }
    })

    Vue.prototype.$only = (obj, keys) => {
      obj = obj || {}
      if (keys === undefined) keys = Object.keys(obj)
      if (typeof keys === 'string') keys = keys.split(/ +/)
      return keys.reduce(function (ret, key) {
        if (obj[key] == null) return ret
        ret[key] = obj[key]
        return ret
      }, {})
    }

    Vue.prototype.$getToken = () => Vue.ls.get(accessToken)

    // 空函数
    Vue.prototype.$noop = f => f

    // 通用日期格式化
    Vue.prototype.$formatTime = formatTime

    /**
     * 高阶请求函数
     *
     * 经过axios响应拦截后try中处理状态码200的请求
     * catch中处理非200非失效等的请求
     * 如果传入失败处理函数则调用否则error message
     * @param {function} fn
     * @param {function | string} loading
     * @param {beforeSend: (query) => query, onError, onOk, onComplete: function} handler
     */
    Vue.prototype.Catch = function (fn, handler = {}) {
      const ctx = this
      return async function (loading) {
        function toggleLoading (show) {
          const hasLoading = typeof loading === 'string' || typeof loading === 'function'
          if (!hasLoading) return
          const loadingString = typeof loading === 'string' ? loading : loading.call(ctx)
          let { k: key, o: target } = getPropByPath(ctx, loadingString)
          target[key] = show
        }

        try {
          toggleLoading(true)
          let args = [].slice.call(arguments)
          let argsExcludeLoading = args.slice(1)
          if (isFunction(handler.beforeSend)) {
            argsExcludeLoading = handler.beforeSend.apply(ctx, argsExcludeLoading)
          }

          const ret = await fn.apply(ctx, [].concat(argsExcludeLoading))

          toggleLoading(false)

          if (!ret) return

          if (isFunction(handler.onOk)) {
            handler.onOk.call(ctx, ret)
          }

          if (isFunction(handler.onComplete)) {
            handler.onComplete.call(ctx, ret)
          }
          return ret
        } catch (err) {
          if (/timeout.*/.test(err.message)) {
            err.message = REQ_TIMEOUT
          }
          if (err.message !== REQUEST_CANCEL_MESSAGE) {
            if (isFunction(handler.onError)) {
              handler.onError.call(ctx, err)
            } else {
              onceError(err.message)
            }
          }
          toggleLoading(false)
          return err
        }
      }
    }

    /**
     * 检测操作列是否存在并设置宽
     *
     * @param {any[]} columns
     * @param {string[]} checkingAuthorities
     */
    Vue.prototype.filterColumn = function (columns, checkingAuthorities) {
      if (this.$store.getters['isSuper']) {
        return columns
      }

      columns = this.$cloneDeep(columns)

      if (typeof checkingAuthorities === 'string') {
        checkingAuthorities = checkingAuthorities.split(/ +/g)
      }

      if (!checkingAuthorities.length) {
        return columns
      }

      const permissions = this.$store.getters.permissions
      const actionCol = columns.slice(-1)[0]

      if (actionCol.prop) return columns

      let actionCount = 0

      checkingAuthorities.forEach(auth => {
        if (permissions.indexOf(auth) > -1) {
          actionCount++
        }
      })
      actionCol.width = actionCount * 80
      if (actionCount === 0) {
        return columns.slice(0, -1)
      }
      return columns
    }

    /**
     * 快捷生成box
     *
     * onOk返回Promise时默认开启按钮的loading
     * @param title
     * @param message
     * @param type
     * @param {function} onOk
     * @param onCancel
     * @param showCancelButton
     * @param okText
     * @param cancelText
     * @param loadingText
     */
    Vue.prototype.MessageBox = ({
      title = '',
      message,
      type,
      customClass = 'v-message-box',
      onOk = noop,
      onCancel = noop,
      showClose = false,
      showCancelButton = true,
      okText = '确认',
      cancelText = '取消',
      loadingText = ''
    }) => {
      return MessageBox({
        title,
        message,
        showClose,
        customClass: !title ? `${customClass} no-heading` : customClass,
        type,
        showCancelButton,
        confirmButtonText: okText,
        cancelButtonText: cancelText,
        beforeClose: (action, instance, done) => {
          if (action === 'confirm') {
            const maybePromise = onOk()
            if (isPromise(maybePromise)) {
              instance.confirmButtonLoading = true
              instance.confirmButtonText = loadingText
              return maybePromise.then(() => {
                instance.confirmButtonLoading = false
                done()
              }).catch(() => {
                instance.confirmButtonLoading = false
                done()
              })
            } else {
              done()
            }
          } else {
            done()
          }
        }
      }).catch(reason => onCancel(reason))
    }

    Vue.prototype.ConfirmToggleOnline = function (options) {
      const willOnline = options.online
      options.message = willOnline ? '确认要上线吗？' : '确认要下线吗？'
      options.type = 'warning'
      this.MessageBox(options)
    }

    Vue.prototype.ConfirmShowInHome = function (options) {
      const willShow = options.show
      options.message = willShow ? '确认要在首页展示吗？' : '确认要取消首页展示吗？'
      options.type = 'warning'
      this.MessageBox(options)
    }

    Vue.prototype.ConfirmDelete = function (options) {
      options.message = '确认要删除所选的记录吗？'
      options.type = 'warning'
      this.MessageBox(options)
    }

    Vue.prototype.ConfirmRemove = function (options) {
      options.message = '确认要移除所选的记录吗？'
      options.type = 'warning'
      this.MessageBox(options)
    }

    Vue.prototype.ConfirmComplete = function (options) {
      options.message = '确认要完成所选的记录吗？'
      options.type = 'warning'
      this.MessageBox(options)
    }

    Vue.prototype.ConfirmSubmit = function (options) {
      options.message = '确认要提交所选的记录吗？'
      options.type = 'warning'
      this.MessageBox(options)
    }
    Vue.prototype.ConfirmLogout = function (options) {
      options.message = '确认要退出系统吗？'
      options.type = 'warning'
      this.MessageBox(options)
    }

    Vue.prototype.getActionDecorator = function (actions = []) {
      actions = [].concat(actions)
      const h = this.$createElement
      return (row, column) => {
        return h('div', {
          class: 'v-table__actions'
        }, [actions.map(decorator => {
          if (typeof decorator.render === 'function') {
            const shouldRender = decorator.render(row, column)
            if (!shouldRender) return null
          }
          return (
            <Wave>{cloneElement(<el-button />, {
              props: {
                size: decorator.size || 'small',
                type: typeof decorator.type === 'function'
                  ? decorator.type(row, column)
                  : (decorator.type),
                icon: decorator.icon,
                disabled: typeof decorator.disabled === 'function'
                  ? decorator.disabled(row, column)
                  : (decorator.disabled || false)
              },
              style: typeof decorator.style === 'function'
                ? decorator.style(row, column)
                : (decorator.style || {}),
              directives: decorator.auth ? [{ name: 'auth', arg: decorator.auth }] : [],
              on: {
                ...(decorator.on || {}),
                click: debounceCallFunction(() => decorator.action && decorator.action(row, column))
              },
              children: [`${decorator.label || ''}`]
            })}
            </Wave>
          )
        })
        ])
      }
    }

    /**
     * enum 转 select
     *
     * @param {*} e
     * @param {*} param1
     */
    Vue.prototype.$mapEnumToSelect = function (e, {
      keyFilter,
      keyTransfer = f => f,
      valueTransfer
    } = {}) {
      if (keyFilter === undefined) {
        keyFilter = f => !isNaN(parseInt(f, 10))
      } else if (keyFilter === null || typeof keyFilter !== 'function') {
        keyFilter = f => f
      }

      if (valueTransfer === undefined) {
        valueTransfer = this.$toNumber
      } else if (valueTransfer === null || typeof valueTransfer !== 'function') {
        valueTransfer = f => f
      }

      let keys = Object.keys(e)

      keys = keys.filter(keyFilter).map(keyTransfer)

      return keys.map(key => ({
        label: e[key],
        value: valueTransfer(key)
      }))
    }

    Vue.prototype.$V = {
      staticBaseURL: staticBaseURL,
      uploadURL: uploadURL,
      size: {
        sm: '650px',
        md: '800px',
        lg: '1200px'
      },
      cellSize: {
        phone: 110,
        status: 80,
        desc: 200,
        logic: 50,
        price: 70,
        date: 150,
        action: 120
      }
    }

    Vue.prototype.$P = authority
  }
}

export default Plugin
